package com.ruoyi.framework.datasource.aspectj;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.annotation.AreaSharding;
import com.ruoyi.common.annotation.DynamicDataSourceAnno;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.datasource.AreaSourceConfig;
import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder;
import com.ruoyi.system.domain.vo.AreaSourceVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.*;

/**
 * @author jierui
 * @package com.ruoyi.framework.datasource.aspectj
 * @date 2022/4/14-16:07
 * @description:
 */
@Aspect
@Component
@Slf4j
public class AreaShardingAspect {


    @Autowired
    AreaSourceConfig areaSourceConfig;

    @Around("@annotation(com.ruoyi.common.annotation.AreaSharding)&& @annotation(areaSharding)")
    public Object around(ProceedingJoinPoint point, AreaSharding areaSharding) throws Throwable {


        // 获取切入的 Method
        MethodSignature joinPointObject = (MethodSignature) point.getSignature();
        Method method = joinPointObject.getMethod();


        boolean flag = method.isAnnotationPresent(AreaSharding.class);
        if (flag) {
            areaSharding = method.getAnnotation(AreaSharding.class);

        } else {
            // 如果方法上没有注解，则搜索类上是否有注解
            areaSharding = AnnotationUtils.findAnnotation(joinPointObject.getMethod().getDeclaringClass(), AreaSharding.class);
        }

        if (!Optional.ofNullable(areaSharding).isPresent()) {
            return point.proceed();
        }
        Map<String, Object> params = getNameAndValue(point);

        String bdsId = MapUtils.getString(params, "bdsId", "");
        String bdsIds = MapUtils.getString(params, "bdsIds", "");


        if (StringUtils.isNotEmpty(bdsId)) {
            AreaSourceVo areaSource = areaSourceConfig.getAreaSource(bdsId);
            try {
                DynamicDataSourceContextHolder.setDataSourceType(areaSource.getSourceCode());
                return point.proceed();
            } catch (Throwable th) {
                log.error("AreaShardingAspect:bdsId==================>>>", th);
            } finally {
                DynamicDataSourceContextHolder.clearDataSourceType();
            }

        }


        if (StringUtils.isNotEmpty(bdsIds)) {
            Set<String> sourceCode = this.getSourceCode(areaSourceConfig.getAreaSourceMap());
            List list = new ArrayList<>();
            Map map = new HashMap();
            Set set = new HashSet();
            if (CollectionUtils.isNotEmpty(sourceCode)) {
                for (String source : sourceCode) {
                    try {
                        DynamicDataSourceContextHolder.setDataSourceType(source);
                        Object proceed = point.proceed();
                        if (proceed instanceof List) {
                            list.addAll((List<?>) proceed);
                        }
                        if (proceed instanceof Map) {
                            map.putAll((Map) proceed);
                        }
                        if (proceed instanceof Set) {
                            set.addAll((Set) proceed);
                        }
                    } catch (Throwable th) {
                        log.error("AreaShardingAspect:bdsIds==================>>>", th);

                    } finally {
                        DynamicDataSourceContextHolder.clearDataSourceType();
                    }
                }
            }

            if (CollectionUtils.isNotEmpty(list)) {
                return list;
            }

            if (CollectionUtils.isNotEmpty(set)) {
                return set;
            }
            if (MapUtils.isNotEmpty(map)) {
                return map;
            }
        }

        return point.proceed();
    }

    /**
     * 根据area进行数据分组
     *
     * @param areaSourceMap
     * @return
     */
    private Set<String> getSourceCode(Map<String, AreaSourceVo> areaSourceMap) {
        Set<String> areaSet = new HashSet<>();
        areaSourceMap.entrySet().stream().forEach(area -> {
            areaSet.add(area.getValue().getSourceCode());
        });
        return areaSet;
    }

    /**
     * 获取参数集合
     *
     * @param joinPoint
     * @return
     */
    private Map<String, Object> getNameAndValue(ProceedingJoinPoint joinPoint) {
        Map<String, Object> param = new HashMap<>();
        //Object[] paramValues = joinPoint.getArgs();
        //String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        //for (int i = 0; i < paramNames.length; i++) {
        //    param.put(paramNames[i], paramValues[i]);
        // }

        String jsonStr = JSON.toJSONString(joinPoint.getArgs());
        if (StringUtils.isNotEmpty(jsonStr)) {
            JSONArray jsonArray = JSONObject.parseArray(jsonStr);
            for(int i=0;i<jsonArray.size();i++){
                JSONObject jsonObject=jsonArray.getJSONObject(i);
                String bdsId = jsonObject.getString("bdsId");
                if(StringUtils.isNotEmpty(bdsId)){
                    param.put("bdsId", bdsId);
                }
                String bdsIds = jsonObject.getString("bdsIds");
                if(StringUtils.isNotEmpty(bdsIds)){
                    param.put("bdsIds", bdsIds);
                }

            }

        }

        return param;
    }
}
